/*
 * Copyright © 2003-2009 Israfil Consulting Services Corporation
 * Copyright © 2003-2009 Christian Edward Gruber
 * All Rights Reserved
 * 
 * This software is licensed under the Berkeley Standard Distribution license,
 * (BSD license), as defined below:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this 
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of Israfil Consulting Services nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 * 
 * $Id: DynamicallyMutableObject.java 127 2006-11-09 18:04:43Z cgruber $
 */
package net.israfil.foundation.dynamic;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.israfil.foundation.core.Strings;
import net.israfil.foundation.core.Types;

/**
 * An abstract class which implements the DynamicallyMutableObject 
 * infrastructure.  It sets a field's value, by mutator method if such 
 * exists, or directly if none such exists.
 * 
 * @author <a href="mailto:cgruber@israfil.net">Christian Edward Gruber </a>
 *
 */
public abstract class DynamicallyMutableObject extends DynamicallyAccessibleObject implements DynamicallyMutable {
	private static Logger logger = Logger.getLogger(DynamicallyMutableObject.class.getName());
   

	public void setNull(String attributeName, Class<?> valueType) {
		setNull(this,attributeName,valueType);
	}
	public void setNull(Object target, String attributeName, Class<?> valueType) {
		if (valueType == null) 
			throw new IllegalArgumentException("Cannot give a valueType of null for setNull(String,Class) method.");
		set(target,attributeName, null, valueType);
	}
	
	public void set(String attributeName,Object value) {
		set(this,attributeName,value);
	}
	public static void set(Object target, String attributeName,Object value) {
		if (value == null) set(target,attributeName, value, null);
		else set(target,attributeName, value, value.getClass());
	}
	
	public void set(String attributeName, Object value, Class<?> valueType) {
		set(this,attributeName,value,valueType);
	}
	public static void set(Object target, String attributeName, Object value,
            Class<?> valueType) {
		if (value == null) {
			if (valueType != null && valueType.isPrimitive()) throw new IllegalArgumentException("Attempted to set null on a variable or accessor for attribute '"+attributeName+"' of primitive type: " + valueType);
			String selector = getMutatorSelector(target,attributeName,valueType);
			if (selector != null) {
				Method m = DynamicUtil.getMethodForSelector(target,selector);
				Class<?>[] parmTypes = m.getParameterTypes();
				if (parmTypes[0].isPrimitive()) 
					throw new IllegalArgumentException("Attempted to set null using an accessor for attribute '"+attributeName+"' of primitive type: " + parmTypes[0]);
				DynamicUtil.performOn(target,selector,new Object[]{value});
			} else {
				Field f = DynamicUtil.getField(target,attributeName);
				if (f == null || f.getType().isPrimitive())
					throw new IllegalArgumentException("Attempted to set null but could not find a set"+Strings.camel(attributeName)+" method with a single non-primitive type.");
				else 
					_setField(target,attributeName,valueType);
			}
		} else /* value != null */ {
			if (valueType == null) valueType = value.getClass();
			if (hasMutator(target,attributeName,valueType)) {
				DynamicUtil.performOn(target,getMutatorSelector(target,attributeName,valueType),new Object[]{value});
				return;
			} else if(hasMutator(target,attributeName,DynamicUtil.getPrimitiveTypeEquivalent(valueType))) {
				DynamicUtil.performOn(target,getMutatorSelector(target,attributeName,DynamicUtil.getPrimitiveTypeEquivalent(valueType)),new Object[]{value});
				return;
			} else {
				_setField(target,attributeName,value);
			}		
		}
	}
	
	protected void _setField(String attributeName,Object value) {
		_setField(this,attributeName,value);
	}
	
	protected static void _setField(Object target, String attributeName,Object value) {
		Field f = DynamicUtil.getField(target,attributeName);
		try {
			if (f == null) {
				RuntimeException rte = new RuntimeException("Could not find field named " + attributeName,
						new NoSuchFieldException(target + " has no property " + attributeName));
				logger.log(Level.FINEST,"Failed to set field.",rte);
				throw rte;
			}
			f.set(target,Types.convert(value,f.getType()));
		} catch (IllegalAccessException e) {
			logger.log(Level.FINEST,"Object attempted to dynamically access a inaccessible field.",e);
			throw new RuntimeException(e);
		}		
	}
	
	protected static final String mutatePrefix =  "set";
	protected String getMutatorSelector(String attributeName, Class<?> type) {
		return getMutatorSelector(this,attributeName,type);
	}
	protected static String getMutatorSelector(Object target,
            String attributeName, Class<?> type) {
		if (attributeName == null || attributeName.equals("")) return null;
		String camelAttr = Strings.camel(attributeName);
		String selector = null;
		if (type == null) {
			Method method = _tryToFindMutator(target,attributeName);
			if (method == null) {
				return null;
			} else /* method != null */ {
				if (method.getParameterTypes().length != 1) return null;
				else return "set"+Strings.camel(attributeName)+":"+method.getParameterTypes()[0].getName();
			}
		} else /* type != null */ {
			
			String tmp = mutatePrefix+camelAttr+":"+type.getName();
			if (DynamicUtil.respondsTo(target,tmp)) selector = tmp;	
			for (Class<?> c : DynamicUtil.getAllParentTypes(type)) {
                tmp = mutatePrefix + camelAttr + ":" + c.getName();
                if (DynamicUtil.respondsTo(target, tmp))
                    selector = tmp; 
			}
		}
		return selector;
	}	
 
	protected static Method _tryToFindMutator(Object target, String attributeName) {
		Method method = null;
		Method[] methods = target.getClass().getMethods();
		for (int i = 0; i < methods.length; i++) {
			if (methods[i].getName().equals("set"+Strings.camel(attributeName))) {
				if (method == null) method = methods[i];
				else throw new IllegalArgumentException("Attempted to find "+mutatePrefix+Strings.camel(attributeName) + " without a given type, but more than one such method exists.  Should use set(String,Object,Class)");
			} 
		}
		return method;
	}
	
	protected static Method _tryToFindMutator(Object target,
            String attributeName, Class<?> type) {
		if (type == null) throw new IllegalArgumentException("Must set a type, or use _tryToFindMutator(Object,String).");
		String methodName = mutatePrefix+Strings.camel(attributeName);
		Method m = DynamicUtil.getMethodForSelector(target,methodName+":"+type.getName());
		for (Class<?> c : DynamicUtil.getAllParentTypes(type)) {
			m = DynamicUtil.getMethodForSelector(target,methodName+":"+c.getName());
		}
		return m;
	}

	public static Method getMutator(Object target, String attributeName,
            Class<?> type) {
		if (type == null) {
			return _tryToFindMutator(target,attributeName);
		} else {
			return _tryToFindMutator(target,attributeName,type);
		}
	}
 
	public boolean hasMutator(String attributeName, Class<?> type) {
		return hasMutator(this,attributeName,type);
	}
	public static boolean hasMutator(Object target, String attributeName,
            Class<?> type) {
		return getMutatorSelector(target,attributeName,type) != null;
	}
}
